/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/* JOrbis
 * Copyright (C) 2000 ymnk, JCraft,Inc.
 *  
 * Written by: 2000 ymnk<ymnk@jcraft.com>
 *   
 * Many thanks to 
 *   Monty <monty@xiph.org> and 
 *   The XIPHOPHORUS Company http://www.xiph.org/ .
 * JOrbis has been based on their awesome works, Vorbis codec.
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
   
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package com.jcraft.jorbis;

public class DspState {
	static final float M_PI = 3.1415926539f;
	static final int VI_TRANSFORMB = 1;
	static final int VI_WINDOWB = 1;

	int analysisp;
	Info vi;
	int modebits;

	float[][] pcm;
	int pcm_storage;
	int pcm_current;
	int pcm_returned;

	float[] multipliers;
	int envelope_storage;
	int envelope_current;

	int eofflag;

	int lW;
	int W;
	int nW;
	int centerW;

	long granulepos;
	long sequence;

	long glue_bits;
	long time_bits;
	long floor_bits;
	long res_bits;

	// local lookup storage
	float[][][][][] window; // block, leadin, leadout, type
	Object[][] transform;
	CodeBook[] fullbooks;
	// backend lookups are tied to the mode, not the backend or naked mapping
	Object[] mode;

	// local storage, only used on the encoding side. This way the
	// application does not need to worry about freeing some packets'
	// memory and not others'; packet storage is always tracked.
	// Cleared next call to a _dsp_ function
	byte[] header;
	byte[] header1;
	byte[] header2;

	public DspState() {
		transform = new Object[2][];
		window = new float[2][][][][];
		window[0] = new float[2][][][];
		window[0][0] = new float[2][][];
		window[0][1] = new float[2][][];
		window[0][0][0] = new float[2][];
		window[0][0][1] = new float[2][];
		window[0][1][0] = new float[2][];
		window[0][1][1] = new float[2][];
		window[1] = new float[2][][][];
		window[1][0] = new float[2][][];
		window[1][1] = new float[2][][];
		window[1][0][0] = new float[2][];
		window[1][0][1] = new float[2][];
		window[1][1][0] = new float[2][];
		window[1][1][1] = new float[2][];
	}

	static float[] window(int type, int window, int left, int right) {
		float[] ret = new float[window];
		switch (type) {
		case 0:
		// The 'vorbis window' (window 0) is sin(sin(x)*sin(x)*2pi)
		{
			int leftbegin = window / 4 - left / 2;
			int rightbegin = window - window / 4 - right / 2;

			for (int i = 0; i < left; i++) {
				float x = (float) ((i + .5) / left * M_PI / 2.);
				x = (float) Math.sin(x);
				x *= x;
				x *= M_PI / 2.;
				x = (float) Math.sin(x);
				ret[i + leftbegin] = x;
			}

			for (int i = leftbegin + left; i < rightbegin; i++) {
				ret[i] = 1.f;
			}

			for (int i = 0; i < right; i++) {
				float x = (float) ((right - i - .5) / right * M_PI / 2.);
				x = (float) Math.sin(x);
				x *= x;
				x *= M_PI / 2.;
				x = (float) Math.sin(x);
				ret[i + rightbegin] = x;
			}
		}
			break;
		default:
			// free(ret);
			return (null);
		}
		return (ret);
	}

	// Analysis side code, but directly related to blocking. Thus it's
	// here and not in analysis.c (which is for analysis transforms only).
	// The init is here because some of it is shared

	int init(Info vi, boolean encp) {
		this.vi = vi;
		modebits = Util.ilog2(vi.modes);

		transform[0] = new Object[VI_TRANSFORMB];
		transform[1] = new Object[VI_TRANSFORMB];

		// MDCT is tranform 0

		transform[0][0] = new Mdct();
		transform[1][0] = new Mdct();
		((Mdct) transform[0][0]).init(vi.blocksizes[0]);
		((Mdct) transform[1][0]).init(vi.blocksizes[1]);

		window[0][0][0] = new float[VI_WINDOWB][];
		window[0][0][1] = window[0][0][0];
		window[0][1][0] = window[0][0][0];
		window[0][1][1] = window[0][0][0];
		window[1][0][0] = new float[VI_WINDOWB][];
		window[1][0][1] = new float[VI_WINDOWB][];
		window[1][1][0] = new float[VI_WINDOWB][];
		window[1][1][1] = new float[VI_WINDOWB][];

		for (int i = 0; i < VI_WINDOWB; i++) {
			window[0][0][0][i] = window(i, vi.blocksizes[0], vi.blocksizes[0] / 2, vi.blocksizes[0] / 2);
			window[1][0][0][i] = window(i, vi.blocksizes[1], vi.blocksizes[0] / 2, vi.blocksizes[0] / 2);
			window[1][0][1][i] = window(i, vi.blocksizes[1], vi.blocksizes[0] / 2, vi.blocksizes[1] / 2);
			window[1][1][0][i] = window(i, vi.blocksizes[1], vi.blocksizes[1] / 2, vi.blocksizes[0] / 2);
			window[1][1][1][i] = window(i, vi.blocksizes[1], vi.blocksizes[1] / 2, vi.blocksizes[1] / 2);
		}

		fullbooks = new CodeBook[vi.books];
		for (int i = 0; i < vi.books; i++) {
			fullbooks[i] = new CodeBook();
			fullbooks[i].init_decode(vi.book_param[i]);
		}

		// initialize the storage vectors to a decent size greater than the
		// minimum

		pcm_storage = 8192; // we'll assume later that we have
		// a minimum of twice the blocksize of
		// accumulated samples in analysis
		pcm = new float[vi.channels][];
		{
			for (int i = 0; i < vi.channels; i++) {
				pcm[i] = new float[pcm_storage];
			}
		}

		// all 1 (large block) or 0 (small block)
		// explicitly set for the sake of clarity
		lW = 0; // previous window size
		W = 0; // current window size

		// all vector indexes; multiples of samples_per_envelope_step
		centerW = vi.blocksizes[1] / 2;

		pcm_current = centerW;

		// initialize all the mapping/backend lookups
		mode = new Object[vi.modes];
		for (int i = 0; i < vi.modes; i++) {
			int mapnum = vi.mode_param[i].mapping;
			int maptype = vi.map_type[mapnum];
			mode[i] = FuncMapping.mapping_P[maptype].look(this, vi.mode_param[i], vi.map_param[mapnum]);
		}
		return (0);
	}

	public int synthesis_init(Info vi) {
		init(vi, false);
		// Adjust centerW to allow an easier mechanism for determining output
		pcm_returned = centerW;
		centerW -= vi.blocksizes[W] / 4 + vi.blocksizes[lW] / 4;
		granulepos = -1;
		sequence = -1;
		return (0);
	}

	DspState(Info vi) {
		this();
		init(vi, false);
		// Adjust centerW to allow an easier mechanism for determining output
		pcm_returned = centerW;
		centerW -= vi.blocksizes[W] / 4 + vi.blocksizes[lW] / 4;
		granulepos = -1;
		sequence = -1;
	}

	// Unike in analysis, the window is only partially applied for each
	// block. The time domain envelope is not yet handled at the point of
	// calling (as it relies on the previous block).

	public int synthesis_blockin(Block vb) {
		// Shift out any PCM/multipliers that we returned previously
		// centerW is currently the center of the last block added
		if (centerW > vi.blocksizes[1] / 2 && pcm_returned > 8192) {
			// don't shift too much; we need to have a minimum PCM buffer of
			// 1/2 long block

			int shiftPCM = centerW - vi.blocksizes[1] / 2;
			shiftPCM = (pcm_returned < shiftPCM ? pcm_returned : shiftPCM);

			pcm_current -= shiftPCM;
			centerW -= shiftPCM;
			pcm_returned -= shiftPCM;
			if (shiftPCM != 0) {
				for (int i = 0; i < vi.channels; i++) {
					System.arraycopy(pcm[i], shiftPCM, pcm[i], 0, pcm_current);
				}
			}
		}

		lW = W;
		W = vb.W;
		nW = -1;

		glue_bits += vb.glue_bits;
		time_bits += vb.time_bits;
		floor_bits += vb.floor_bits;
		res_bits += vb.res_bits;

		if (sequence + 1 != vb.sequence)
			granulepos = -1; // out of sequence; lose count

		sequence = vb.sequence;

		{
			int sizeW = vi.blocksizes[W];
			int _centerW = centerW + vi.blocksizes[lW] / 4 + sizeW / 4;
			int beginW = _centerW - sizeW / 2;
			int endW = beginW + sizeW;
			int beginSl = 0;
			int endSl = 0;

			// Do we have enough PCM/mult storage for the block?
			if (endW > pcm_storage) {
				// expand the storage
				pcm_storage = endW + vi.blocksizes[1];
				for (int i = 0; i < vi.channels; i++) {
					float[] foo = new float[pcm_storage];
					System.arraycopy(pcm[i], 0, foo, 0, pcm[i].length);
					pcm[i] = foo;
				}
			}

			// overlap/add PCM
			switch (W) {
			case 0:
				beginSl = 0;
				endSl = vi.blocksizes[0] / 2;
				break;
			case 1:
				beginSl = vi.blocksizes[1] / 4 - vi.blocksizes[lW] / 4;
				endSl = beginSl + vi.blocksizes[lW] / 2;
				break;
			}

			for (int j = 0; j < vi.channels; j++) {
				int _pcm = beginW;
				// the overlap/add section
				int i = 0;
				for (i = beginSl; i < endSl; i++) {
					pcm[j][_pcm + i] += vb.pcm[j][i];
				}
				// the remaining section
				for (; i < sizeW; i++) {
					pcm[j][_pcm + i] = vb.pcm[j][i];
				}
			}

			// track the frame number... This is for convenience, but also
			// making sure our last packet doesn't end with added padding. If
			// the last packet is partial, the number of samples we'll have to
			// return will be past the vb->granulepos.
			//
			// This is not foolproof! It will be confused if we begin
			// decoding at the last page after a seek or hole. In that case,
			// we don't have a starting point to judge where the last frame
			// is. For this reason, vorbisfile will always try to make sure
			// it reads the last two marked pages in proper sequence

			if (granulepos == -1) {
				granulepos = vb.granulepos;
			} else {
				granulepos += (_centerW - centerW);
				if (vb.granulepos != -1 && granulepos != vb.granulepos) {
					if (granulepos > vb.granulepos && vb.eofflag != 0) {
						// partial last frame. Strip the padding off
						_centerW -= (granulepos - vb.granulepos);
					} // else{ Shouldn't happen *unless* the bitstream is out of
						// spec. Either way, believe the bitstream }
					granulepos = vb.granulepos;
				}
			}

			// Update, cleanup

			centerW = _centerW;
			pcm_current = endW;
			if (vb.eofflag != 0)
				eofflag = 1;
		}
		return (0);
	}

	// pcm==NULL indicates we just want the pending samples, no more
	public int synthesis_pcmout(float[][][] _pcm, int[] index) {
		if (pcm_returned < centerW) {
			if (_pcm != null) {
				for (int i = 0; i < vi.channels; i++) {
					index[i] = pcm_returned;
				}
				_pcm[0] = pcm;
			}
			return (centerW - pcm_returned);
		}
		return (0);
	}

	public int synthesis_read(int bytes) {
		if (bytes != 0 && pcm_returned + bytes > centerW)
			return (-1);
		pcm_returned += bytes;
		return (0);
	}

	public void clear() {
	}
}
